package cyce.file;

import java.io.*;

import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;

/**
 * FileFactory handles creating and modifying Directories and Files
 * FileFactory does not handle propogating these changes across 
 * the distributed system
 * @author Colin Rhodes
 *
 */
public abstract class FileFactory {
	
	/**
	 * Create a directory structure from an XML string where the root element of
	 * that XML string represents a Directory
	 * @param xml
	 * @return
	 */
	public static Directory deserializeDirectory(String xml) {
		Element rootElement;
		try {
			SAXBuilder builder = new SAXBuilder();
			Document doc = builder.build(new StringReader(xml));
			rootElement = doc.getRootElement();
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		
		try {
			return Directory.deserialize(rootElement, null);
		}
		catch (IllegalArgumentException iea) {
			throw new IllegalArgumentException("XML does not represent a valid Directory structure");
		}
	}
	
	/**
	 * Create a directory structure from an XML string where the root element of
	 * that XML string represents a File
	 * @param xml
	 * @return
	 */
	public static File deserializeFile(String xml) {
		Element rootElement;
		try {
			SAXBuilder builder = new SAXBuilder();
			Document doc = builder.build(new StringReader(xml));
			rootElement = doc.getRootElement();
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		
		try {
			return File.deserialize(rootElement, null);
		}
		catch (IllegalArgumentException iea) {
			throw new IllegalArgumentException("XML does not represent a valid File structure");
		}
	}
	
	/**
	 * Serialize a directory structure (including children) into an XML string
	 * @param dir
	 * @return
	 */
	public static String serialize(Directory dir) {
		Element root = dir.serialize();
		
		XMLOutputter outputter = new XMLOutputter();
		StringWriter swriter = new StringWriter();
		try {
		outputter.output(new Document(root), swriter);
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
			return null;
		}
		
		return swriter.toString();
	}

	/**
	 * Add a File to a directory structure.
	 * This method does not handle communication with the rest of the distributed system
	 * @param root The root directory of the file structure
	 * @param path The path of the directory into which the file will be inserted
	 * @param fileName The name of the file being inserted
	 * @param hash The hash of the file being inserted
	 * @return
	 */
	public static File addFile(Directory root, String parentPath, String fileName, String hash) {
		if (!root.isRoot())
			throw new IllegalArgumentException("root is not a root directory");
		if (fileName == null || fileName.equals(""))
			throw new IllegalArgumentException("fileName must be a non-empty string");
		if (hash == null)
			throw new IllegalArgumentException("hash cannot be null");
		if (root.getFileByHash(hash) != null)
			throw new IllegalArgumentException("A file with the given hash already exists");

		File parentFile = root.getFile(parentPath);
		
		if ((parentFile == null) || !(parentFile instanceof Directory))
			throw new IllegalArgumentException("path does not refer to a valid directory");
		
		Directory parentDir = (Directory)parentFile;
		
		return parentDir.addFile(fileName, hash);
	}
	
	/**
	 * Remove a File from a directory structure.
	 * This method does not handle communication with the rest of the distributed system
	 * @param root The root directory of the file structure
	 * @param path The path of the file to remove, including the filename
	 * @return
	 */
	public static void removeFile(Directory root, String path) {
		if (!root.isRoot())
			throw new IllegalArgumentException("root is not a root directory");

		root.removeFile(path);
	}
	
	/**
	 * Add a File to given directory.
	 * This method does not handle communication with the rest of the distributed system
	 * @param parent The directory into which the file will be inserted
	 * @param fileName The name of the file being inserted
	 * @param hash The hash of the file being inserted
	 * @return
	 */
	public static File addFile(Directory parent, String fileName, String hash) {
		return parent.addFile(fileName, hash);
	}
	
	/**
	 * Add a Directory to a directory structure.
	 * This method does not handle communication with the rest of the distributed system
	 * @param root The root directory of the file structure
	 * @param path The path of the directory into which the new directory will be inserted
	 * @param dirName The name of the directory being inserted
	 * @return
	 */
	public static Directory addDirectory(Directory root, String path, String dirName) {
		if (!root.isRoot())
			throw new IllegalArgumentException("root is not a root directory");
		if (dirName == null || dirName.equals(""))
			throw new IllegalArgumentException("dirName must be a non-empty string");

		File parentFile = root.getFile(path);
		
		if ((parentFile == null) || !(parentFile instanceof Directory))
			throw new IllegalArgumentException("path does not refer to a valid directory");
		
		Directory parentDir = (Directory)parentFile;
		
		return parentDir.addDirectory(dirName);
	}
	/**
	 * Add a Directory to given directory.
	 * This method does not handle communication with the rest of the distributed system
	 * @param parent The directory into which the file will be inserted
	 * @param fileName The name of the directory being inserted
	 * @return
	 */
	public static Directory addFile(Directory parent, String dirName) {
		if (dirName == null || dirName.equals(""))
			throw new IllegalArgumentException("dirName must be a non-empty string");

		return parent.addDirectory(dirName);
	}
	
	/**
	 * Find the parent Directory of a file
	 * @param root Root of the directory structure
	 * @param file File to find the parent of
	 * @return
	 */
	public static Directory getParent(Directory root, File file) {
		// root doesn't have a parent
		if (file.isRoot()) return null;
		
		Directory parent = null;
		
		try {
			parent = (Directory)root.getFile(file.getParentPath());
		}
		catch (ClassCastException cce) {
			// the file referenced by the parent path should always be a directory
			assert false;
		}
		
		return parent;
	}
	
	/**
	 * List the directory structure for testing purposes
	 * @return
	 */
	public static String listStructure(File file) {
		return file.list("");
	}
	
	public static Directory createRootDirectory() {
		return new Directory("", null);
	}
	
	public static boolean fileExists(Directory root, File file) {
		File byPath = root.getFile(file.getPath());
		if (byPath == null) return false;
		
		if (byPath.getHash().equals(file.getHash()))
			return true;
		else
			return false;
	}
}
